package com.samh.openrower2;


import java.util.List;
import java.util.ArrayList;

import com.samh.openrower2.util.SpeedHandler;
import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.content.Context;
import android.util.Log;
import android.widget.SlidingDrawer;
import android.location.Criteria;
import android.os.Handler;
import android.os.Binder;


public class SpeedSensor implements LocationListener {
	
	private static final String TAG = "SpeedSensor";
	public boolean gpsLocked = false; 
	//private final boolean simulateGPS = false;
	private Location currentLocation;
	private Location lastLocation;
	private int locationsCount = 0;
	private Handler mHandler = new Handler();
	private SpeedHandler mSpeedHandler = new SpeedHandler();
	public static float lastAccuracy;
	private long currentLocationTS;
	private long lastLocationTS;
	//private long lastSpeedTS;
	private boolean lockLocationUpdate = false;
	private boolean updateDistance = false;
	private boolean serviceRunning = false;
	
	private List<Location> locationList = new ArrayList<Location>();
	private List<Float> speedList = new ArrayList<Float>();
	
	private float lastSpeed = 0.0f;
	private float averageSpeed = 0.0f;
	private float lastRawSpeed = 0.0f;
	private float lastGpsSpeed = 0.0f;
	private double totalDistance = 0;
	
	private LocationManager locationManager;
	//private Handler mHandler = new Handler();
	private static final int REQ_LOCS_TO_MEASURE = 10; // minimum of 10
	private static final float LOCATION_MIN_DISTANCE = 0.0f;
	private static final long LOCATION_MIN_TIME = 0;
	private final static int MAX_SPEEDS_TO_AVG = 100;
	private final static int CURRENT_SPEEDS_TO_AVG = 15;
	public static final float MAX_SPEED = 50.0f;
	public static final float MIN_SPEED = 0.07f;
	public static final double MIN_DISTANCE = 0.5;
	public static float MIN_ACCURACY = 80;
	//private static long LOOP_DURATION = 2000;
	
	/**
	 * 
	 * @return
	 */
	public float getLastSpeed() {
		return lastSpeed;
	}
	
	private void setLastSpeed(float speed) {
		Log.d(TAG, "Set Last Speed: " + speed);
		lastSpeed = speed;
	}
	
	/**
	 * 
	 * @return
	 */
	public float getLastRawSpeed() {
		return lastRawSpeed;
	}
	
	/**
	 * 
	 * @return
	 */
	public float getLastGpsSpeed() {
		return lastGpsSpeed;
	}
	
	/**
	 * 
	 * @return
	 */
	public float getAverageSpeed() {
		return averageSpeed;
	}
	
	/**
	 * 
	 */
	public void resetAverageSpeed() {
		speedList.clear();
		averageSpeed = 0;
	}
	
	/**
	 * 
	 * @return
	 */
	public float getLastAccuracy() {
		return lastAccuracy;
	}
	
	/**
	 * 
	 * @return
	 */
	public double getDistance() {
		return totalDistance;
	}
	
	/**
	 * 
	 * @return void 
	 */
	public void resetTotalDistance() {
		totalDistance = 0;
	}
	
	/**
	 * Will call this on speed = 0 or on reset
	 */
	public void resetLocationsCount() {
		locationsCount = 0;
	}
	
	/**
	 * 
	 */
	public void stopUpdatingDistance() {
		Log.d(TAG,"Stop Updating Distance");
		updateDistance = false;
	}
	
	/**
	 * 
	 */
	public void startUpdatingDistance() {
		Log.d(TAG,"Start Updating Distance");
		updateDistance = true;
	}
	
	public void startListener(Context context) {
		
		startGpsListener(context);
		
		//updateSpeedSimulator();
		
		//mHandler.post(simulatorLoop);
	}
	
	public void stopListener() {
		
		stopGpsListener();
	}
	
	private void stopGpsListener() {
		
		if(serviceRunning) {
			Log.d(TAG,"GPS Sensor Stopping");
			locationManager.removeUpdates(this);
		}
		
		serviceRunning = false;
	}
	
	private void updateSpeedSimulator() {
		Log.d(TAG,"Speed Simulator Updating");
		
		lastAccuracy = 10f;
		
		float speed = (float) 3.0f + (float)  Math.random();
		
		Log.d(TAG, "Raw Speed: "+speed);
		Log.d(TAG, "Raw Split: " + mSpeedHandler.convertSpeedToSplit(speed));
		
		speedHandler(speed);
		
		distanceHandler(speed);
		
		//startSpeedSimulator();
	}
	
	/**
	 * 
	 * @param context
	 */
	private void startGpsListener(Context context) {
		
		if(!serviceRunning) {
			
			Log.d(TAG,"GPS Sensor Starting");
			
			Criteria criteria = new Criteria();
			
			criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
			
			criteria.setAccuracy(Criteria.ACCURACY_FINE);
			
			locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
			
			if(!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
				//TODO: need an alert here
				Log.d(TAG,"GPS IS NOT ENABLED");
			} 
			
			locationManager.requestLocationUpdates(
					LocationManager.GPS_PROVIDER,
					LOCATION_MIN_TIME,
					LOCATION_MIN_DISTANCE,
					this);
	
			serviceRunning = true;
			
		}
		
	}

	@Override
	public void onLocationChanged(Location location) {
		
		lastAccuracy = location.getAccuracy();
		
		/**
		 * this ensures we have one location before processing
		 * speed and distance data
		 */
		if(!gpsLocked) {
			
			currentLocation = lastLocation = location;
			currentLocationTS = lastLocationTS = SystemClock.elapsedRealtime();
			gpsLocked = true;
			
		} else {
			
			currentLocation = location;
			updateLocation();
		}
		
	}
	

	public void updateLocation() {
		
		// exit if the GPS accuracy is out of bounds
		if(lastAccuracy > MIN_ACCURACY) {
			Log.d(TAG, "Waiting for GPS Accuracy: " + currentLocation.getAccuracy() + " > " + MIN_ACCURACY);
			return;
		}
		
		//exit if we're already updating or GPS is not locked
		if (lockLocationUpdate==true | !gpsLocked) {
			return;
		}
		
		// add this location to the list
		locationList.add(currentLocation);
		
		lastGpsSpeed = currentLocation.getSpeed();
		
		/*
		 *  if we have received enough locations or if the count is under
		 *  the  required locations then do the processing
		 */
		float metres = 0.0f;
		int locsMinus=0;
		int locs = REQ_LOCS_TO_MEASURE - 1;
		float[] distance = new float[1];
		
		if(locationList.size() >= REQ_LOCS_TO_MEASURE) {
									
			// stops any future updates while processing
			lockLocationUpdate = true;
			
			// for estimating speed
			currentLocationTS = SystemClock.elapsedRealtime();
			
			Location.distanceBetween(
					lastLocation.getLatitude(), 
					lastLocation.getLongitude(), 
					currentLocation.getLatitude(), 
					currentLocation.getLongitude(), 
					distance);
			
			metres = distance[0];
			
			// calculate the speed in m/s
			float markSecs = (currentLocationTS - lastLocationTS);
					
			float calSpeed = ((metres * 1000) / markSecs);
			
			// filter out speeds too fast or slow 
			if((calSpeed < MIN_SPEED | calSpeed > MAX_SPEED)) {
				//Log.d(TAG, "Speed out of bounds: "+calSpeed);
				lockLocationUpdate = false;
				speedHandler(0f);
				return;
			} else {
				
				speedHandler(calSpeed);
				
				distanceHandler(metres);
				
			}
			
			// clear the location list
			locationList.clear();
			
			// unlock the process
			lockLocationUpdate = false;
			
			// reset the TS 
			lastLocationTS = currentLocationTS;
				
			lastLocation = currentLocation;
			
		}
		
	}
	
	private void distanceHandler(float distance) {
		
		// do some rounding
		double metres = Math.round(distance * 1000);
		double rndMetres = metres / 1000;
		
		if(updateDistance && rndMetres > MIN_DISTANCE) {
			totalDistance += rndMetres;
		}
			
		if(totalDistance>=99999) {
			totalDistance = 0;
		}

	}
		
	private void speedHandler(float speed) {
		
		lastRawSpeed = speed;
		Log.d(TAG, "Last RAW speed: "+ lastRawSpeed);
		
		if(lastGpsSpeed == 0.0f && lastSpeed > 0) {
			//Log.d(TAG, "Set speed (speedhandler 1): "+0f);
			//setLastSpeed(lastGpsSpeed);
			//return;
		}
		
		// do some rounding
		double rnd = Math.round(speed * 100);
		float rndSpeed = (float) rnd / 100;
		
		if(lastRawSpeed > 0) {
			addSpeedtoList(rndSpeed);
		}
		
		int size = speedList.size();
		
		if(size>=CURRENT_SPEEDS_TO_AVG && rndSpeed > MIN_SPEED) {
			// we have enough to average the recent speeds
			
			float recentAvgSpeed = 0;
			
			for(int i = 1; i <= CURRENT_SPEEDS_TO_AVG; i++) {
				recentAvgSpeed += speedList.get(size - i);
			}
			
			float is = ((recentAvgSpeed /  CURRENT_SPEEDS_TO_AVG) + lastGpsSpeed) / 2;
			
			setLastSpeed(is);
			
			speedList.set(size - 1, lastSpeed);
			
			Log.d(TAG, "Last split: " + mSpeedHandler.convertSpeedToSplit(lastSpeed));
			
		} else {
			setLastSpeed(rndSpeed);
			Log.d(TAG, "Last Non-AVG split: " + mSpeedHandler.convertSpeedToSplit(rndSpeed));
			Log.d(TAG, "Size of speedList: "+ speedList.size());
			
		}
		
		
		
	}
	
	private float addSpeedtoList(float speed) {
		
		float speeds = 0f;
		
		if(speedList.size() == MAX_SPEEDS_TO_AVG) {
			speedList.remove(0);
		}
		
		speedList.add(speed);

		
		for(int i = 0; i < speedList.size(); i++) {
			
			speeds += speedList.get(i);
		}
		
		averageSpeed = speeds / speedList.size();
		
		return averageSpeed;
	}
	
	@Override
	public void onProviderDisabled(String provider) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onProviderEnabled(String provider) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub

	}
	
	Runnable simulatorLoop = new Runnable() {
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
						
			updateSpeedSimulator();
			
			mHandler.postDelayed(this, 3000);
		}
	};
		

}
